#include "select.h"
#include "qtch/log.h"
#include "util.h"
#include "table.h"

#include <set>

namespace qtch {
namespace orm {

static Logger::ptr logger = QTCH_LOG_NAME("orm");

Select::Select(std::shared_ptr<Table> table){
    m_table = table;
}

bool Select::init(const tinyxml2::XMLElement& node) {
    if(strcasecmp(node.Name(),"select")){
        return false;
    }

    if(!node.Attribute("id")){
        QTCH_LOG_ERROR(logger) << "select id not exists";
        return false;
    }
    m_id = node.Attribute("id");
    QTCH_LOG_DEBUG(logger) << "Select init id=" << m_id;

    if(node.Attribute("resultType")){
        std::string type = node.Attribute("resultType");
        if(type==m_table->getName()){
            m_resultType = Column::Type::TYPE_TABLE;
        }else{
            Column::Type cType = Column::ParseType(type);
            if(cType==Column::Type::TYPE_NULL){
                QTCH_LOG_ERROR(logger) << "select unknow resultType=" << type;
                return false;
            }
            m_resultType = cType;
        }
    }

    if(node.Attribute("resultList")){
        m_isResultList = node.BoolAttribute("resultList");
    }
    const tinyxml2::XMLNode * label = node.FirstChild();
    while(label){
        Label::ptr label_ptr = Label::Create(*label);
        if(!label_ptr){
            QTCH_LOG_ERROR(logger) << "Select init faile,select id=" << m_id;
            return false;
        }
        std::vector<OrmParameter::ptr> temp = label_ptr->getBindVec();
        for(size_t i=0;i<temp.size();++i){
            if(temp[i]->getType()!=Column::Type::TYPE_NULL){
                continue;
            }
            std::vector<Column::ptr> colVec = m_table->getCols();
            int flag = 0;
            for(size_t j=0;j<colVec.size();j++){
                if(colVec[j]->getName()==temp[i]->getName()){
                    temp[i]->setType(Column::Type::TYPE_TABLE);
                    flag =1;
                    break;
                }
            }
            if(flag){
                m_usingEntiry = true;
            }else{
                QTCH_LOG_ERROR(logger) << "select id=" << m_id << " init param name=" << temp[i]->getName() \
                            <<" faile by unkonw type";
                return false;
            }
        }
        m_label.push_back(label_ptr);
        label = label->NextSibling();
    }
    return true;

}

std::string Select::gen_ParameterList(){
    std::stringstream ss;

    ss << GetAsClassName(m_id) << "(";
    
    std::vector<OrmParameter::ptr> parmVec;
    for(size_t i=0;i<m_label.size();++i){
        std::vector<OrmParameter::ptr> temp = m_label[i]->getBindVec();
        for(size_t j=0;j<temp.size();j++){
            parmVec.push_back(temp[j]);
        }
    }
    std::set<std::string> paramNameSet;
    if(m_isResultList){
        paramNameSet.insert("__orm_result");

        ss << "std::vector<";
        if(m_resultType==Column::Type::TYPE_TABLE){
            ss << m_table->getClassName() << "::ptr";
        }else {
            ss << Column::TypeToString(m_resultType);
        }
        ss << ">& __orm_result, ";
    } 

    if(m_usingEntiry){
        paramNameSet.insert("__orm_entiry");
        ss << m_table->getClassName()<< "::ptr __orm_entiry, ";
    }


    for(size_t i=0;i<parmVec.size();++i){
        if(paramNameSet.count(parmVec[i]->getName())==1){
            continue;
        }
        if(parmVec[i]->getType()==Column::Type::TYPE_NULL
         ||parmVec[i]->getType()==Column::Type::TYPE_TABLE){
            continue;
        }
        ss << Column::TypeToString(parmVec[i]->getType()) << " ";
        ss << parmVec[i]->getName() << ", ";

    }
    ss << "qtch::IDB::ptr __orm_conn)";

    return ss.str();
}

std::string Select::gen_inc() {
    std::stringstream ss;
    if(m_isResultList){
        ss << "int";
    } else{
        if(m_resultType==Column::Type::TYPE_TABLE){
            ss << m_table->getClassName() << "::ptr";
        }else {
            ss << Column::TypeToString(m_resultType);
        }
    }
    ss << " " << gen_ParameterList();
    return ss.str();
    
}

std::string Select::gen_src() {
    std::stringstream ss;

    if(m_isResultList){
        ss << "int";
    } else{
        if(m_resultType==Column::Type::TYPE_TABLE){
            ss << m_table->getClassName() << "::ptr";
        }else {
            ss << Column::TypeToString(m_resultType);
        }
    }

    ss << " ";
    ss << m_table->getClassDaoName() << "::";
    ss << gen_ParameterList();
    ss << " {" << std::endl;
    ss << "    qtch::IStmt::ptr __orm_stmt;" << std::endl;
    ss << "    std::stringstream __orm_sql;" << std::endl;
    ss << "    int __orm_paramNum = 0;" << std::endl;
    ss << "    __orm_paramNum++;" << std::endl;
    ss << "    __orm_paramNum--;" << std::endl;


    ss << std::endl;
    for(size_t i = 0; i < m_label.size(); ++i){
        ss << m_label[i]->gen(m_table);
        ss << std::endl;
    }
    
    ss << "    auto rt = __orm_stmt->query();" << std::endl;
    ss << "    if(!rt) {" << std::endl;
    if(m_isResultList){
        ss << "        return 0;" << std::endl;
    } else {
        if(m_resultType <= Column::Type::TYPE_DOUBLE) {
            ss << "        return 0;" << std::endl;
        }else if(m_resultType <=  Column::Type::TYPE_BLOB)  {
            ss << "        return \"\";" << std::endl;
        } else {
            ss << "        return nullptr;" << std::endl;
        }
    }
    ss << "    }" << std::endl;

    if(!m_isResultList){
        ss << "    if(!rt->next()) {" << std::endl;
        ss << "        return 0;" << std::endl;
        ss << "    }" << std::endl; 
    } else {
        ss << "    while(rt->next()){" << std::endl;
    }

    if(m_resultType == Column::Type::TYPE_TABLE){
        ss << (m_isResultList? "    " : "");
        ss << "    " << m_table->getClassName() << "::ptr __orm_value" 
           << " = " << m_table->getClassName() << "::ptr(new " << m_table->getClassName() << ");" << std::endl;
        std::vector<qtch::orm::Column::ptr> cols = m_table->getCols();
        for(size_t i = 0; i < cols.size(); ++i){
            ss << (m_isResultList? "    " : "");
            ss << "    __orm_value->" << GetAsMemberName(cols[i]->getName()) << " = rt->" 
               << Column::getGetString(cols[i]->getDType()) << "(\"" << cols[i]->getName() << "\");" << std::endl;
            ss << (m_isResultList? "    " : "");
            ss << "    " << "__orm_value->" << getAsSetNullFunName(cols[i]->getName()) << "(rt->isNull(\"" << cols[i]->getName() << "\"));"<< std::endl;
        }
        
    } else {
        ss << (m_isResultList? "    " : "");
        ss << "    " << Column::TypeToString(m_resultType) << " __orm_value = rt->" 
           << Column::getGetString(m_resultType) << "(0);" << std::endl;
    }
    
    if(!m_isResultList){
        ss << "    return __orm_value;" << std::endl;
    } else {
        ss << "        __orm_result.push_back(__orm_value);" << std::endl;
        ss << "    }" << std::endl;
        ss << "    return 1;";
    }


    ss << std::endl << "}";
    return ss.str();
}



}
}